#include "GError.h"
#include "GTask.h"
#include "GEvent.h"
#include "GSystem.h"
#include "GTaskQueue.h"
#include "GTaskManager.h"
#include "GWorkerThread.h"
#include "GSynchronizer.h"

namespace GEngine
{
	GTaskManager::GTaskManager()
	{
	}

	GTaskManager::~GTaskManager()
	{
	}

	void GTaskManager::WaitAllWorkers()
	{
		// Wait for idle events untill all of them are signaled
		GThread::GSynch::WaitFor((GThread::GWaitable**)IdleEvents.data(), IdleEvents.size(), true);
	}

	void GTaskManager::WakeAllWorkers()
	{
		// Free active so workers waiting on Active can resume
		ActiveSemaphore.Free((GUInt32)WorkerCount - 1);
	}

	void GTaskManager::ExitAllWorkers()
	{
		Exiting.store(true, std::memory_order_release);
		/*
		for (GSize I = 0; I < WorkerCount-1; ++I)
		{
			Workers[I]->Exit();
		}
		*/
	}

	// Start doing work at current thread untill result is done.
	// Any Worker can schedule a task and wait for it to complete
	void GTaskManager::BusyWait(GTaskResult* Result)
	{
		GUtil::GError::DebugCheck(Result == NULL, "NULL pointer.");

		// Work until the given result is complete
		while(!GTaskResult::CheckComplete(*Result))
		{
			mGCurrentWorker->Execute(Result);
		}

		// A case for main thread
		// If the result is the root task
		if (RootResult == Result)
		{
			// Let workers go idle and synch
			RootResult = NULL;
		}
	}

	bool GTaskManager::CheckExiting()
	{
		return Exiting.load(std::memory_order_acquire);
	}

	bool GTaskManager::CheckRootComplete()
	{
		return RootResult == nullptr;
	}

	GSize GTaskManager::GetWorkerCount() const
	{
		return WorkerCount;
	}

	// Task can be added to all workers but calling worker is prefered
	// Workers are already running
	bool GTaskManager::Schedule(GTask* Task, GSize ThreadId /*= mGCurrentWorkerId*/)
	{
		GUtil::GError::DebugCheck(Task == NULL, "NULL Pointer.");
		GUtil::GError::DebugCheck(ThreadId >= WorkerCount, "Index out of range.");

		// Schedule and set state accordingly
		bool Queued = Queues[ThreadId]->Push(Task);
		if (!Queued)
		{
			return false;
		}

		Task->IncreaseRef();

		return true;
	}

	// Only main thread can can schedule root task
	// wakes all workers
	bool GTaskManager::ScheduleRoot(GTask* Task)
	{
		GUtil::GError::DebugCheck(Task == NULL, "NULL Pointer.");
		GUtil::GError::DebugCheck(RootResult != NULL, "There is already a scheduled root task.");
		GUtil::GError::DebugCheck(mGCurrentWorkerId != 0, "Only main thread can schedule root task.");
		
		// Wait workers to be ready
		WaitAllWorkers();

		// Can we distribute it ?
		// All workers are idle there wont be contention
		bool Spreaded = Task->Spread(Queues.data(), WorkerCount);
		if (!Spreaded)
		{
			// If cannot distribute then schedule
			bool Scheduled = Schedule(Task, 0);
			if (!Scheduled)
			{
				return false;
			}
		}

		// Set root task and wake workers
		RootResult = Task->Result;

		WakeAllWorkers();
		
		return true;
	}

	bool GTaskManager::Initialize() OVERRIDE
	{
		if (!GInitializable::Initialize())
		{
			return false;
		}

		Exiting.store(false, std::memory_order_release);

		WorkerCount = GUtil::GSystem::GetCoreCount();
		GUtil::GError::DebugCheck(WorkerCount == 0, "Zero worker count");

		Queues.reserve(WorkerCount);
		Workers.reserve(WorkerCount);
		IdleEvents.reserve(WorkerCount-1);

		// Allocate workers and queues
		for (GSize I = 0; I < WorkerCount; ++I)
		{
			GTaskQueue* NewQueue = new GTaskQueue();
			GWorkerThread* NewWorker = new GWorkerThread();
			
			Queues.push_back(NewQueue);
			Workers.push_back(NewWorker);
		}

		ActiveSemaphore.Create(0, (GUInt32)WorkerCount - 1, nullptr);

		// Worker 0 is main thread, does not use idle or active events
		Queues[0]->Create(Queues.data(), WorkerCount);
		Workers[0]->AttachMainThread(Queues[0]);

		// Initialize queues and threads
		for (GSize I = 1; I < WorkerCount; ++I)
		{
			GThread::GEvent* WorkerIdleEvent = nullptr;

			// Worker 0 will be attached to main thread automatically
			Queues[I]->Create(Queues.data(), WorkerCount);
			Workers[I]->Create(I, Queues[I], &ActiveSemaphore, &WorkerIdleEvent);

			IdleEvents.push_back(WorkerIdleEvent);
		}

		return true;
	}

	void GTaskManager::Uninitialize() OVERRIDE
	{
		// let workers exit
		WaitAllWorkers();
		ExitAllWorkers();
		WakeAllWorkers();

		// Cleanup threads
		for (GSize I = 1; I < WorkerCount; ++I)
		{
			// Implicitly waits on workers actual thread untill it exits
			Workers[I]->Release();
			Queues[I]->Release();
			
			delete Workers[I];
			delete Queues[I];
		}

		ActiveSemaphore.Release();

		IdleEvents.clear();
		Queues.clear();
		Workers.clear();
		
		GInitializable::Uninitialize();
	}

}
